<!DOCTYPE html>
<html>

<head>
  <title>JS装饰器 decorator /dekəreɪtər/</title>
  <meta charset='utf-8'>
  <link href='https://cdn.maxiang.io/res-min/themes/marxico.css' rel='stylesheet'>
  <style>
    #preview-contents {
      margin-top: -20px;
      width: 900px;
    }
  </style>
</head>

<body>
  <div id='preview-contents' class='note-content'>
    <h4 id="js装饰器-decorator-dekəreɪtər">JS装饰器 decorator /dekəreɪtər/</h4>
    <blockquote>
      <p>JavaScript中的装饰器：就是对类、类属性、类方法之类的一种装饰，可以理解为在原有代码外层又包装了一层处理逻辑。这样就可以做到不直接修改代码，就实现某些功能。</p>
    </blockquote>

    <p><strong>在vscode中支持装饰器</strong></p>
    <p><img width="600" longdesc="./1671120459619.png" alt="Alt text" title="" type="image/png"
        src=""
        class=""></p>

    <p><strong>在create-react-app中支持装饰器</strong></p>
    <blockquote>
      <p>$ yarn add <code>@babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties</code> <br>
        $ yarn add roadhog@2.5.0-beta.1 处理版本兼容问题</p>
    </blockquote>
    <p>package.json</p>
    <pre class="prettyprint hljs-dark"><code class="hljs prolog"><div class="hljs-line"><span class="hljs-string">"babel"</span>: {
</div><div class="hljs-line">  <span class="hljs-string">"presets"</span>: [ <span class="hljs-string">"react-app"</span> ],
</div><div class="hljs-line">  <span class="hljs-string">"plugins"</span>: [
</div><div class="hljs-line">    [
</div><div class="hljs-line">      <span class="hljs-string">"@babel/plugin-proposal-decorators"</span>,
</div><div class="hljs-line">      {
</div><div class="hljs-line">        <span class="hljs-comment">/* legacy /ˈleɡəsi/：使用历史遗留（Stage-1）的装饰器中的语法和行为。它为提案从 Stage-1 到当前阶段 Stage-2 平稳过渡作铺垫*/</span>
</div><div class="hljs-line">        <span class="hljs-string">"legacy"</span>: true
</div><div class="hljs-line">      }
</div><div class="hljs-line">    ],
</div><div class="hljs-line">    [
</div><div class="hljs-line">       <span class="hljs-string">"@babel/plugin-proposal-class-properties"</span>,
</div><div class="hljs-line">       {
</div><div class="hljs-line">         <span class="hljs-comment">/* loose=false时，是使用Object.defineProperty定义属性，loose=ture，则使用赋值法直接定义 */</span>
</div><div class="hljs-line">         <span class="hljs-string">"loose"</span>: true
</div><div class="hljs-line">       }
</div><div class="hljs-line">     ]
</div><div class="hljs-line">  ]
</div><div class="hljs-line">}
</div></code></pre>

    <p><strong>1. 类的装饰器</strong> <br>
      类装饰器在类声明之前被声明，可以用来监视，修改或替换类的定义。</p>
    <pre class="prettyprint hljs-dark"><code class="language-javascript hljs"><div class="hljs-line"><span class="hljs-keyword">const</span> classDecorator = <span class="hljs-function">(<span class="hljs-params">target</span>) =&gt;</span> {
</div><div class="hljs-line">    <span class="hljs-comment">// target:被修饰的类「Demo」</span>
</div><div class="hljs-line">    target.num = <span class="hljs-number">100</span>; <span class="hljs-comment">//给类设置静态私有属性</span>
</div><div class="hljs-line">    <span class="hljs-comment">// return function () { } //返回的值将替换现有Demo的值</span>
</div><div class="hljs-line">};
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line">@classDecorator
</div><div class="hljs-line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Demo</span> </span>{}
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line"><span class="hljs-comment">/*</span>
</div><div class="hljs-line"><span class="hljs-comment">编译后的结果:</span>
</div><div class="hljs-line"><span class="hljs-comment">var _class;</span>
</div><div class="hljs-line"><span class="hljs-comment">const classDecorator = target =&gt; {</span>
</div><div class="hljs-line"><span class="hljs-comment">  target.num = 100;</span>
</div><div class="hljs-line"><span class="hljs-comment">  // return function () { }</span>
</div><div class="hljs-line"><span class="hljs-comment">};</span>
</div><div class="hljs-line"><span class="hljs-comment">let Demo = classDecorator(_class = class Demo {}) || _class;</span>
</div><div class="hljs-line"><span class="hljs-comment">*/</span>
</div></code></pre>
    <pre class="prettyprint hljs-dark"><code class="language-javascript hljs"><div class="hljs-line"><span class="hljs-keyword">const</span> classDecorator = <span class="hljs-function">(<span class="hljs-params">target</span>) =&gt;</span> {
</div><div class="hljs-line">    target.num = <span class="hljs-number">100</span>;
</div><div class="hljs-line">};
</div><div class="hljs-line"><span class="hljs-keyword">const</span> classDecorator2 = <span class="hljs-function">(<span class="hljs-params">target</span>) =&gt;</span> {
</div><div class="hljs-line">    <span class="hljs-comment">// 给类的原型上设置公共属性/方法</span>
</div><div class="hljs-line">    target.prototype.say = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">say</span>(<span class="hljs-params"></span>) </span>{ };
</div><div class="hljs-line">};
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line">@classDecorator <span class="hljs-comment">//第一个装饰器</span>
</div><div class="hljs-line">@classDecorator2 <span class="hljs-comment">//第二个装饰器</span>
</div><div class="hljs-line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Demo</span> </span>{
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line">}
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line"><span class="hljs-comment">/*</span>
</div><div class="hljs-line"><span class="hljs-comment">编译后的结果:</span>
</div><div class="hljs-line"><span class="hljs-comment">var _class;</span>
</div><div class="hljs-line"><span class="hljs-comment">const classDecorator = target =&gt; {</span>
</div><div class="hljs-line"><span class="hljs-comment">  target.num = 100;</span>
</div><div class="hljs-line"><span class="hljs-comment">};</span>
</div><div class="hljs-line"><span class="hljs-comment">const classDecorator2 = target =&gt; {</span>
</div><div class="hljs-line"><span class="hljs-comment">  target.prototype.say = function say() { };</span>
</div><div class="hljs-line"><span class="hljs-comment">};</span>
</div><div class="hljs-line"><span class="hljs-comment">let Demo = classDecorator(_class = classDecorator2(_class = class Demo {}) || _class) || _class;</span>
</div><div class="hljs-line"><span class="hljs-comment">*/</span>
</div></code></pre>
    <p>还可以让装饰器接受参数，这就等于可以修改装饰器的行为了，这也叫做装饰器工厂。装饰器工厂是通过在装饰器外面再封装一层函数来实现。</p>
    <pre class="prettyprint hljs-dark"><code class="language-javascript hljs"><div class="hljs-line"><span class="hljs-keyword">const</span> classDecorator = <span class="hljs-function">(<span class="hljs-params">x, y</span>) =&gt;</span> {
</div><div class="hljs-line">    <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">target</span>) =&gt;</span> {
</div><div class="hljs-line">        target.total = x + y;
</div><div class="hljs-line">    };
</div><div class="hljs-line">};
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line">@classDecorator(<span class="hljs-number">100</span>, <span class="hljs-number">200</span>)
</div><div class="hljs-line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Demo</span> </span>{ }
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line"><span class="hljs-comment">/*</span>
</div><div class="hljs-line"><span class="hljs-comment">编译后的结果:</span>
</div><div class="hljs-line"><span class="hljs-comment">var _dec, _class;</span>
</div><div class="hljs-line"><span class="hljs-comment">const classDecorator = (x, y) =&gt; {</span>
</div><div class="hljs-line"><span class="hljs-comment">  return target =&gt; {</span>
</div><div class="hljs-line"><span class="hljs-comment">    target.total = x + y;</span>
</div><div class="hljs-line"><span class="hljs-comment">  };</span>
</div><div class="hljs-line"><span class="hljs-comment">};</span>
</div><div class="hljs-line"><span class="hljs-comment">let Demo = (_dec = classDecorator(100, 200), _dec(_class = class Demo {}) || _class);</span>
</div><div class="hljs-line"><span class="hljs-comment">*/</span>
</div></code></pre>

    <p><strong>2. 类属性/方法装饰器</strong> <br>
      类属性装饰器可以用在类的单个成员上，无论是类的属性、方法….该装饰器函数有3个参数：</p>
    <ul>
      <li>
        <p>target：成员所在的类</p>
      </li>
      <li>
        <p>name：类成员的名字</p>
      </li>
      <li>
        <p>descriptor：属性描述符</p>
      </li>
    </ul>
    <p>使用类属性装饰器可以做很多有意思的事情，最经典的例子就是@readonly</p>
    <pre class="prettyprint hljs-dark"><code class="language-javascript hljs"><div class="hljs-line"><span class="hljs-keyword">const</span> readonly = <span class="hljs-function">(<span class="hljs-params">target, name, descriptor</span>) =&gt;</span> {
</div><div class="hljs-line">    <span class="hljs-comment">// target:Demo</span>
</div><div class="hljs-line">    <span class="hljs-comment">// name:'x'</span>
</div><div class="hljs-line">    <span class="hljs-comment">// descriptor:{configurable: true, enumerable: true, writable: true, initializer: ƒ}</span>
</div><div class="hljs-line">    descriptor.writable = <span class="hljs-literal">false</span>;
</div><div class="hljs-line">};
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Demo</span> </span>{
</div><div class="hljs-line">    @readonly x = <span class="hljs-number">100</span>;
</div><div class="hljs-line">}
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line"><span class="hljs-keyword">let</span> d = <span class="hljs-keyword">new</span> Demo;
</div><div class="hljs-line">d.x = <span class="hljs-number">200</span>; <span class="hljs-comment">//Uncaught TypeError: Cannot assign to read only property 'x' of object</span>
</div></code></pre>
    <p>还可以装饰函数成员</p>
    <pre class="prettyprint hljs-dark"><code class="language-javascript hljs"><div class="hljs-line"><span class="hljs-keyword">const</span> recordTime = <span class="hljs-function">(<span class="hljs-params">target, name, descriptor</span>) =&gt;</span> {
</div><div class="hljs-line">    <span class="hljs-keyword">let</span> func = descriptor.value;
</div><div class="hljs-line">    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> func === <span class="hljs-string">"function"</span>) {
</div><div class="hljs-line">        <span class="hljs-comment">// 重构函数，做计时统计</span>
</div><div class="hljs-line">        descriptor.value = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">proxy</span>(<span class="hljs-params">...params</span>) </span>{
</div><div class="hljs-line">            <span class="hljs-built_in">console</span>.time(name);
</div><div class="hljs-line">            <span class="hljs-keyword">let</span> result = func.call(<span class="hljs-keyword">this</span>, ...params);
</div><div class="hljs-line">            <span class="hljs-built_in">console</span>.timeEnd(name);
</div><div class="hljs-line">            <span class="hljs-keyword">return</span> result;
</div><div class="hljs-line">        };
</div><div class="hljs-line">    }
</div><div class="hljs-line">};
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Demo</span> </span>{
</div><div class="hljs-line">    @recordTime
</div><div class="hljs-line">    init() {
</div><div class="hljs-line">        <span class="hljs-keyword">return</span> <span class="hljs-number">100</span>;
</div><div class="hljs-line">    }
</div><div class="hljs-line">}
</div><div class="hljs-line"><span class="hljs-keyword">let</span> d = <span class="hljs-keyword">new</span> Demo;
</div><div class="hljs-line"><span class="hljs-built_in">console</span>.log(d.init());
</div></code></pre>
    <p>有关多个装饰器的处理顺序： <br>
      写了工厂函数，从上到下依次求值，目的是获取装饰器函数。 <br>
      装饰器函数的执行顺序是由下至上依次执行。</p>
    <pre class="prettyprint hljs-dark"><code class="language-javascript hljs"><div class="hljs-line"><span class="hljs-keyword">const</span> A = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
</div><div class="hljs-line">    <span class="hljs-built_in">console</span>.log(<span class="hljs-number">1</span>);
</div><div class="hljs-line">    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
</div><div class="hljs-line">        <span class="hljs-built_in">console</span>.log(<span class="hljs-number">2</span>);
</div><div class="hljs-line">    };
</div><div class="hljs-line">};
</div><div class="hljs-line"><span class="hljs-keyword">const</span> B = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
</div><div class="hljs-line">    <span class="hljs-built_in">console</span>.log(<span class="hljs-number">3</span>);
</div><div class="hljs-line">    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
</div><div class="hljs-line">        <span class="hljs-built_in">console</span>.log(<span class="hljs-number">4</span>);
</div><div class="hljs-line">    };
</div><div class="hljs-line">};
</div><div class="hljs-line"><wbr>
</div><div class="hljs-line"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Demo</span> </span>{
</div><div class="hljs-line">    @A()
</div><div class="hljs-line">    @B()
</div><div class="hljs-line">    init() { }
</div><div class="hljs-line">}
</div><div class="hljs-line"><span class="hljs-comment">// 1 3 4 2</span>
</div></code></pre>
  </div>
</body>

</html>